package bittwiddled;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Map.Entry;

import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class Dynamic {

	//TODO how to make immutable?
	public static final int[] bitmask; static {
		bitmask = new int[32];
		bitmask[0] = 1;
		for (int i=1; i< bitmask.length ; i++) bitmask[i] = bitmask[i-1] << 1;
	}
	
	private final Map<Integer,Integer> dynamic;
	private final BeforeStates[] befores;
	
	private static final List<Integer>[] temps(int size) {
		@SuppressWarnings("unchecked")
		List<Integer>[] result = (ArrayList<Integer>[])Array.newInstance((new ArrayList<Integer>()).getClass(), size);
		while (size > 0) result[--size] = new ArrayList<Integer>();
		return result;
	}
	
	public Dynamic(int size, int...states) {
		refSize = bitmask.length - size;
		dynamic = new HashMap<Integer,Integer>(states.length);
		befores = new BeforeStates[size];
		List<Integer>[] tempBeforeOff = temps(size);
		List<Integer>[] tempBeforeOn = temps(size);

		int from = checkValue(states[0]);
		
		for (int index = 1; index < states.length - 1; index++) {
			int to = checkValue(states[index]);
			if (dynamic.put(from, to)!=null) throw new IllegalArgumentException();
			for (int i=0; i<size ; i++) {
				((to & bitmask[i]) == bitmask[i] ? tempBeforeOn : tempBeforeOff)[i].add(from);
			}
			from = to;
		}
		
		for (int i=0; i<size ; i++) befores[i] = new BeforeStates(tempBeforeOn[i],tempBeforeOff[i]);
	}
	
	public Dynamic(int size, Iterable<Integer> states) {
		refSize = bitmask.length - size;
		befores = new BeforeStates[size];
		dynamic = new HashMap<Integer,Integer>();
		List<Integer>[] tempBeforeOff = temps(size);
		List<Integer>[] tempBeforeOn = temps(size);

		Iterator<Integer> iter = states.iterator();
		int from = checkValue(iter.next());
		
		while (iter.hasNext()) {
			int to = checkValue(iter.next());
			if (dynamic.put(from, to)!=null) throw new IllegalArgumentException();
			for (int i=0; i<size ; i++) {
				((to & bitmask[i]) == bitmask[i] ? tempBeforeOn : tempBeforeOff)[i].add(from);
			}
			from = to;
		}
		
		for (int i=0; i<size ; i++) befores[i] = new BeforeStates(tempBeforeOn[i],tempBeforeOff[i]);
	}
	
	public Dynamic(int size, Map<Integer,Integer> states) {
		refSize = bitmask.length - size;
		dynamic = new HashMap<Integer,Integer>();
		befores = new BeforeStates[size];
		List<Integer>[] tempBeforeOff = temps(size);
		List<Integer>[] tempBeforeOn = temps(size);

		for (Entry<Integer,Integer> mapping : states.entrySet()) {
			int from = checkValue(mapping.getKey()), to = checkValue(mapping.getValue());
			if (dynamic.put(from, to)!=null) throw new IllegalArgumentException();
			for (int i=0; i<size ; i++) {
				((to & bitmask[i]) == bitmask[i] ? tempBeforeOn : tempBeforeOff)[i].add(from);
			}
		}
		
		for (int i=0; i<size ; i++) befores[i] = new BeforeStates(tempBeforeOn[i],tempBeforeOff[i]);
		
	}
	
	//TODO alternative implementation: allow for expansion of vertices?
	private final int refSize;
	private int checkValue(int newInt) {
		if (Integer.numberOfLeadingZeros(newInt) < refSize) throw new IllegalArgumentException("for "+newInt+" : "+Integer.numberOfLeadingZeros(newInt)+" vs "+refSize);
		return newInt;
	}
	
	public BeforeStates[] get() { return befores; }
	
	public BeforeStates get(int which) { return befores[which]; }
	
	public String toString() {
		int targetlength = bitmask.length;
		StringBuilder result = new StringBuilder();
		for (Entry<Integer,Integer> mapping : dynamic.entrySet()) 
			result.append("\n")
			.append(Integer.toBinaryString(mapping.getKey())+"("+mapping.getKey()+")")
			.append(" => ")
			.append(Integer.toBinaryString(mapping.getValue())+"("+mapping.getValue()+")");
		
		for (int i=0; i<befores.length;i++ ) result.append("\n").append(i+":\n")
			.append("ON: ").append(befores[i].beforeON().toString()).append("\n")
			.append("OFF: ").append(befores[i].beforeOFF().toString());
		
		return result.substring(1);
	}
	
}
